#!/usr/bin/env perl

use v5.14;
use warnings;

use DBI;
use Getopt::Long;
use Config::INI::Reader;
use List::MoreUtils qw(any firstidx);

use constant {
    DEBUG                          => 1,
    POOL_COMPLEXITY_UNKNOWN        => 0,
    POOL_COMPLEXITY_VERY_EASY      => 1,
    POOL_COMPLEXITY_EASY           => 2,
    POOL_COMPLEXITY_DIFFICULT      => 3,
    POOL_COMPLEXITY_VERY_DIFFICULT => 4,
};

GetOptions(
    \my %args,
    'config|c=s',
);

my $conf = Config::INI::Reader->read_file($args{config});
$conf = $conf->{mysql};

my $dbh = DBI->connect(
    "DBI:mysql:database=$conf->{dbname};host=$conf->{host}",
    $conf->{user},
    $conf->{passwd},
    {
        mysql_enable_utf8 => 1,
    },
) or die DBI->errstr;

my %achievement = (
    #lone_warrior     => \&lone_warrior,                  # completed an entire pool without joining any team
    committed        => \&committed,                     # completed an entire pool
    conformist       => \&conformist,                    # divergency <= 1% 
    grammar_nazi     => \&grammar_nazi,                  # error rate <= 1%
    risky_business_1 => \&risky_business_very_difficult, # tried very difficult pool
    risky_business_2 => \&risky_business_difficult,      # tried difficult type
    curiosity        => \&curiosity,                     # tried uknown diffculty pool
    diversity        => \&diversity,                     # tried all pool types
    #team_player      => \&team_player,                   # joined a team
    #team_leader      => \&team_leader,                   # annotated more than your team mates
    #fast_hands_clear_mind => \&fast_hands_clear_mind,    # annotated 100 samples under 10 minutes
    top1             => \&top1,                          # #1 in stats
    top2             => \&top2,                          # #2 in stats
    top3             => \&top3,                          # #3 in stats
    leet             => \&top1337,                       # #1337 in stats
);

my @game_over = (
    [qw(committed)],
    [qw(conformist)],
    [qw(grammar_nazi)],
    [qw(risky_business_1)],
    [qw(risky_business_2)],
    [qw(curiosity)],
    [qw(diversity)],
    [qw(top1 top2 leet)],
);

my %achievements_by_id;
my %name_by_id;

my $sth = $dbh->prepare("
    select
        user_id, user_name
    from
        users
");
$sth->execute;
$sth->bind_columns(\my($id, $name));
while($sth->fetch) {
    my @user_achievements = compute_user_achievements($id);
    $achievements_by_id{$id} = \@user_achievements;
    $name_by_id{$id} = $name;
}

for my $id (sort { $a <=> $b } keys %achievements_by_id) {
    if(@{ $achievements_by_id{$id} }) {
        if(DEBUG) {
            local $" = ', ';
            say "$name_by_id{$id}: @{ $achievements_by_id{$id} }" ;
        }
    }
    else {
        say "$name_by_id{$id}: no achievements" if DEBUG;
    }
}

#$sth = $dbh->prepare("
#    insert ignore
#        user_badges
#    where
#        user_id = ?
#");
#$dbh->begin;
#for my $user (keys %achievements_by_user) {
#    for my $achievement (@{ $achievements_by_user{$user} }) {
#        # FIXME map achievement name to badge id
#        $sth->execute($user, $achievement);
#    }
#}
#$dbh->commit;

sub compute_user_achievements {
    my $user = shift;

#    my $already_has = $dbh->selectcol_arrayref("
#        select
#            badge_id
#        from
#            user_badges
#        where
#            user_id = ?
#    ", undef, $user);

    my @achievements;
    for my $name (keys %achievement) {
#        next if any { $_ eq $name } @$already_has;

        if($achievement{$name}->($user)) {
            push @achievements, $name;
        }
    }

    @achievements;
}

sub lone_warrior {
    my $user = shift;

    my($team) = $dbh->selectrow_array("
        select
            user_team
        from
            users
        where
            user_id = ?
    ");
    return 0 if $team;

    committed($user);
}

sub committed {
    my $user = shift;

    my $finished_pools = $dbh->selectcol_arrayref("
        select
            pool_id
        from
            morph_annot_pools
        where
            status >= 6 -- moderation completed
    ");

    for(@$finished_pools) {
        my $all = $dbh->selectcol_arrayref("
            select
                sample_id
            from
                morph_annot_samples
            where
                pool_id = ?
        ", undef, $_);

        local $" = ',';
        my($done_by_user) = $dbh->selectrow_array("
            select
                count(sample_id)
            from
                morph_annot_instances
            where
                sample_id in (@{ $all })
                and user_id = ?
                and answer > 0
        ", undef, $user);

        return 1 if $done_by_user == @$all;
    }

    0;
}

# FIXME should only be eligible to users with >= 1000 answers
sub conformist {
    my $user = shift;

    my $annotators = _get_stats_param_value($user, 'annotators') // 0;
    my $divergency = _get_stats_param_value($user, 'annotators_divergence') // 0;
    my $value      = eval { $divergency / $annotators * 100 };
    
    ($value // ~0) <= 1;
}

# FIXME should only be eligible to users with >= 1000 answers
sub grammar_nazi {
    my $user = shift;

    my $correct   = _get_stats_param_value($user, 'annotators_correct') // 0;
    my $moderated = _get_stats_param_value($user, 'annotators_moderated') // 0;
    my $value     = eval { (1 - $correct / $moderated) * 100 };

    ($value // 999) <= 1;
}

sub risky_business_difficult {
    my $user = shift;

    my $pools = _count_pools_with_complexity($user, POOL_COMPLEXITY_DIFFICULT);

    $pools > 0;
}

sub risky_business_very_difficult {
    my $user = shift;

    my $pools = _count_pools_with_complexity($user, POOL_COMPLEXITY_VERY_DIFFICULT);

    $pools > 0;
}

sub curiosity {
    my $user = shift;

    my $pools = _count_pools_with_complexity($user, POOL_COMPLEXITY_UNKNOWN);

    $pools > 0;
}

sub diversity {
    my $user = shift;

    for(
        POOL_COMPLEXITY_UNKNOWN,
        POOL_COMPLEXITY_VERY_EASY,
        POOL_COMPLEXITY_EASY,
        POOL_COMPLEXITY_DIFFICULT,
        POOL_COMPLEXITY_VERY_DIFFICULT,
    )
    {
        my $pools = _count_pools_with_complexity($user, $_);
        $pools == 0 and return 0;
    }

    1;
}

sub team_player {
    my $user = shift;

    my $team = _get_user_team($user);

    !!$team;
}

sub team_leader {
    my $user = shift;

    my $team = _get_user_team($user);
    return 0 unless !!$team;

    my $team_mates = $dbh->selectcol_arrayref("
        select
            user_id
        from
            users
        where
            user_team = ?
    ", undef, $team);

    state $positions = _get_stats_positions();

    # FIXME use hashes
    @$positions = grep {
        my $curr = $_;
        any { $_ == $curr } @$team_mates;
    } @$positions;

    $positions->[0] == $user;
}

sub top1 {
    my $user = shift;

    _get_user_stats_position($user) == 1;
}

sub top2 {
    my $user = shift;

    _get_user_stats_position($user) == 2;
}

sub top3 {
    my $user = shift;

    _get_user_stats_position($user) == 3;
}

sub top1337 {
    my $user = shift;

    _get_user_stats_position($user) == 1337;
}

sub _get_stats_positions {
    my $user = shift;

    my $positions = $dbh->selectall_arrayref("
        select
            user_id,
            count(*) as cnt
        from
            morph_annot_instances
        where
            answer > 0
        group by
            user_id
        order by
            cnt desc
    ");

    [map $_->[0], @$positions];
}

sub _get_user_team {
    my $user = shift;

    my($team) = $dbh->selectrow_array("
        select
            user_team
        from
            users
        where
            user_id = ?
    ", undef, $user);

    $team;
}

sub _count_pools_with_complexity {
    my($user, $complexity) = @_;

    my($pools) = $dbh->selectcol_arrayref("
        select
            count(distinct pool_id)
        from
            morph_annot_instances mai
        join
            morph_annot_samples mas
        using
            (sample_id)
        join
            morph_annot_pools map
        using
            (pool_id)
        join
            morph_annot_pool_types mapt
        on
            mapt.type_id = map.pool_type
        where
            mai.user_id = ?
            and mapt.complexity = ?
    ", undef, $user, $complexity);

    $pools;
}

sub _get_stats_param_value {
    my($user, $param) = @_;

    my($value) = $dbh->selectrow_array("
        select
            param_value
        from
            user_stats
        join
            stats_param
        using
            (param_id)
        where
            user_id = ?
            and param_name = ?
    ", undef, $user, $param);

    $value;
}

sub _get_user_stats_position {
    my $user = shift;

    my $positions = _get_stats_positions();

    # FIXME use hashes
    my $position = firstidx { $_ == $user } @$positions;
    ++$position;
}
